跳到主要内容

Scrapy 学习

Scrapy Shell

# 安装 ipython
pip install ipython
# Tab键 可补齐所需代码
# 方向键 可显示命令历史
# Ctrl + u 清除所在行所有内容
# Ctrl + l 清屏
# Ctrl + c 终止当前执行的代码
# Ctrl + shift+v 从剪切板粘贴文本
# Ctrl + f 前移一个字符
# Ctrl + b 后移一个字符

# 启动(如果安装了 `ipython` 这个终端,会默认跳转到那里)
# <url> 是要爬取的网页的地址
scrapy shell <url>

# 注:如果直接执行上面的命令会有很多无用的 log弹出来干扰,所以最好加上 --nolog
scrapy shell <url> --nolog

可用的快捷命令(shortcut)

shelp() - 打印可用对象及快捷命令的帮助列表 fetch(request_or_url) - 根据给定的请求(request)或 URL 获取一个新的 response,并更新相关的对象 view(response) - 在本机的浏览器打开给定的 response。 其会在 response 的 body 中添加一个 <base> tag ,使得外部链接(例如图片及css)能正确显示。 注意,该操作会在本地创建一个临时文件,且该文件不会被自动删除。

Scrapy 终端根据下载的页面会自动创建一些方便使用的对象

crawler - 当前 Crawler 对象. spider - 处理 URL 的 spider。 对当前 URL 没有处理的 Spider 时创建一个 Spider 对象。 request - 最近获取到的页面的 Request 对象。 可以使用 replace() 修改该 request。或使用 fetch 快捷方式来获取新的 request response - 包含最近获取到的页面的 Response 对象。 sel - 根据最近获取到的 Response 构建的 Selector 对象。 settings - 当前的 Scrapy settings

所以可以直接在这里调试,例如调试 xpath

response.xpath('//button[@class="btn-next"]/../@href') 

Setting 文件

参考资料 Settings 单纯就是一个配置文件,里面存放一些公共的变量

一般用全大写字母命名变量例如:SQL_HOST = '192.168.0.1'

如何想要读取某项自定的字段或者已有的字段

from TempSpider.settings import DEFAULT_REQUEST_HEADERS

def process_item(self, item, spider):
# 取得字段
header = DEFAULT_REQUEST_HEADERS()
# 或者直接在 self 里就能取得
self.settings['DEFAULT_REQUEST_HEADERS']
# 也可以使用 spider 取得
spider.settings.get('DEFAULT_REQUEST_HEADERS')

下面记录些常用的配置项

# 项目名    默认为 scrapybot
BOT_NAME

# 是否遵循 robots.txt 策略
ROBOTSTXT_OBEY

# Item Pipeline 同时处理(每个 response 的)item的最大值 默认: 100
CONCURRENT_ITEMS

# Scrapy downloader 并发请求的最大值 默认:16
# 提供了一个粗略的控制,无论如何不会有超过该数目的请求被并发下载
CONCURRENT_REQUESTS

# 对单个网站进行并发请求的最大值 默认:8
# 针对目标域名提供对并发请求数目的更进一步的限制
CONCURRENT_REQUESTS_PER_DOMAIN

# 默认请求头
DEFAULT_REQUEST_HEADERS

# 最大深度,如果为 0 表示不限制 默认:0
DEPTH_LIMIT

# 关键!! 下载器在下载同一个网站下一个页面前需要等待的时间
# 该选项可以用来限制爬取速度 DOWNLOAD_DELAY = 0.25 # 250 ms of delay
DOWNLOAD_DELAY

# 如果启用,当从相同的网站获取数据时,Scrapy将会等待一个随机的值 (0.5 到 1.5 之间的一个随机值 * DOWNLOAD_DELAY)
# 默认 True
# 若 DOWNLOAD_DELAY 为 0(默认值),该选项将不起作用
RANDOMIZE_DOWNLOAD_DELAY


# 超时时间 默认:180
DOWNLOAD_TIMEOUT

# 是否启用 cookies middleware 如果关闭,cookies 将不会发送给 web server 默认: True
COOKIES_ENABLED

# 开启后会将请求携带的 Cookie 打印到 DEBUG Log 上去 默认: False
COOKIES_DEBUG

# 用于检测过滤重复请求的类
# 默认:'scrapy.dupefilters.RFPDupeFilter'
DUPEFILTER_CLASS

# 下载中间件
DOWNLOADER_MIDDLEWARES

# 爬虫中间件
SPIDER_MIDDLEWARES



# 插件
EXTENSIONS

# 是否启用 Log 默认开启
LOG_ENABLED
# 日志使用的编码 默认:utf-8
LOG_ENCODING
# 日志输出的文件名 默认 None
LOG_FILE
# 日志输出格式 默认:'%Y-%m-%d %H:%M:%S'
LOG_FORMAT
# 日志日期格式 默认:'%Y-%m-%d %H:%M:%S'
LOG_DATEFORMAT
# 日志级别 默认:DEBUG
# CRITICAL、 ERROR、WARNING、INFO、DEBUG
LOG_LEVEL
# 如果为 True ,进程所有的标准输出(及错误)将会被重定向到 log 中。例如, 执行 print 'hello'
# 默认是:False
LOG_STDOUT


# 自动限速插件(算法根据以下规则调整下载延迟及并发数:) 默认关闭
# 中文 https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/autothrottle.html?highlight=autothrottle
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
# 设置是否启用
AUTOTHROTTLE_ENABLED
# 初始下载延迟(单位:秒) 默认: 5.0
AUTOTHROTTLE_START_DELAY
# 在高延迟情况下最大的下载延迟(单位秒)。默认 60
AUTOTHROTTLE_MAX_DELAY
# The average number of requests Scrapy should be sending in parallel to each remote server
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# 启用 AutoThrottle 调试(debug)模式,展示每个接收到的 response 默认:False
AUTOTHROTTLE_DEBUG




# 启用和配置 HTTP 缓存
# 中文:https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/downloader-middleware.html?highlight=httpcache_enabled#std:setting-HTTPCACHE_ENABLED
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
HTTPCACHE_ENABLED = True
# 缓存的 request 的超时时间,单位秒。
# 超过这个时间的缓存 request 将会被重新下载。如果为0,则缓存的 request 将永远不会超时
HTTPCACHE_EXPIRATION_SECS = 0
# 存储(底层的)HTTP 缓存的目录。如果为空,则 HTTP 缓存将会被关闭
HTTPCACHE_DIR = 'httpcache'
# 不缓存指定的 HTTP (code)的 request
HTTPCACHE_IGNORE_HTTP_CODES = []
# 实现缓存的类
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'



# 是一个扩展插件,通过 TELENET 可以监听到当前爬虫的一些状态,默认是 True 开启状态
# 默认端口为 6023
# 命令行输入:telnet localhost 6023 就可以检查和控制 Scrapy 运行过程
# 详情参考:https://www.osgeo.cn/scrapy/topics/extensions.html#topics-extensions-ref-telnetconsole
TELNETCONSOLE_ENABLED

Pipeline 管道

这个 Pipeline 就是用来处理引擎传过来的数据(item)的管道

生命周期

# 当 spider 被开启时,这个方法被调用。(只执行一次)
# 注意:这个 spider 参数可以用来创建对象,这样后面就能直接在 spider 爬虫里读取到这个参数了
def open_spider(self, spider):
# 可以在这里对爬虫的参数初始化或者添加参数
spider.hello = 'world' # 这样就能在爬虫执行时通过 self.hello 读取到这个属性
print("当前开启的爬虫为:", spider)


# 每个item pipeline 组件都需要调用该方法
def process_item(self, item, spider):
return item


# 当spider被关闭时,这个方法被调用 (只执行一次)
def close_spider(self, spider):
print("爬虫%s关闭!" % spider)

"""
注意:在进行文件操作时,如果在启动时打开,结束时关闭文件 这样的操作是有一定问题的
因为如果读取到一半文件报错时,就不会关闭文件,这样会使还在内存里的数据丢失
(要 close 之后才会写入数据,否则在大于设置的阈值之前都在内存里)
所以最好手动写入文件 f.flush()
"""

CrawlSpider

Scrapy 框架中分两类爬虫

Spider 类和 CrawlSpider 类。

CrawlSpider 是 Spider 的子类,Spider 类的设计原则是只爬取 start_url 列表中的网页,而 CrawlSpider 类定义了一些规则(rule)来提供跟进 link 的方便的机制,从爬取的网页中获取 link 并继续爬取的工作更适合。

CrawlSpider 是爬取那些具有一定规则网站的常用的爬虫,它基于 Spider 并有一些独特属性

生成 CrawlSpider 的命令

scrapy genspider -t crawl 名字 "网站域名"

参数的介绍

# 是 Rule 对象的集合,用于匹配目标网站的地址
# 注意:不同的 Rule 之间不存在先后顺序,所以就无法传递参数了,
# 如果需要先后顺序的可以继续使用 yield scrapy.Request(...)
# 无顺序的!!!!!!!!!
rules
# 在 rules 中包含一个或多个 Rule 对象
# 每个 Rule 对爬取网站的动作定义了特定的操作。
# 如果多个 rule 匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用
# 参数 link、callback、follow、process_links、process_request

# ========== Rule 主要参数介绍 =========

# 是一个 LinkExtractor 对象,用于定义需要提取的链接
link # 参数看下面 LinkExtractor

# 提取出 url 地址的 response 会交给 callback 处理
# 注意:当编写爬虫规则是,避免使用 parse 作为回调函数。由于 CrawlSpider 使用 parse 方法来实现其逻辑,如果覆盖了 parse方法,CrawlSpider将会运行失败
callback
# response 中提取的链接是否需要跟进
# 即:在不指定 callback 函数的请求下,如果 follow 为 True,满足该 rule 的 url 还会继续被请求
# 默认是 False
follow # 是一个布尔值(boolean)
# 指定该 Spider 中哪个的函数将会被调用,从 link 中获取到链接列表将会调用该函数。该方法主要用来处理 url
process_links # 就是对匹配到 url 再做一些调整

# 例如
rules = (
Rule(LinkExtractor(allow=('/tag/\w+/$',)),
follow=True, # 如果有指定回调函数,默认不跟进
callback='parse_item',
process_links='process_links',),

@staticmethod
def process_links(links): # 对提取到的链接进行处理
for link in links:
link.url = link.url + 'page/1/'
yield link

# 指定该 Spider 中哪个的函数将会被调用,该规则提取到每个 request 是都会调用该函数。(用来过滤 request)
process_request
# 使用例
rules = (
#规则解析器:可以将连接提取器提取到的所有连接表示的页面进行指定规则(回调函数)的解析
Rule(link, callback='parse_item', follow=True),
)

# ========== LinkExtractor 主要参数介绍 =========

# 满足括号中“正则表达式”的值会被提取,如果为空,则会全部匹配。
allow
# 与这个正则表达式(或正则表达式列表)不匹配的 URL 一定不提取(优先级高于 allow)
deny
# 会被提取的链接 domains
allow_domains
# 一定不会被提取链接的 domains
deny_domains
# 使用 xpath 表达式,和 allow 共同作用过滤链接
restrick_xpaths

使用例:

# -*- coding: utf-8 -*-
import scrapy
# 导入 CrawlSpider 相关模块
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

# 该爬虫程序是基于 CrawlSpider 类的
class CrawldemoSpider(CrawlSpider):
name = 'crawlDemo' # 爬虫文件名称
# allowed_domains = ['example.com']
start_urls = ['http://www.example.com/']

# 连接提取器:会去起始 url 响应回来的页面中提取指定的 url
# 虽然这里匹配的不是完整的链接,但是 CrawlSpider 会自动将其补充完整
link = LinkExtractor(allow=r'/8hr/page/\d+')
# rules 元组中存放的是不同的规则解析器(封装好了某种解析规则)
rules = (
#规则解析器:可以将连接提取器提取到的所有连接表示的页面进行指定规则(回调函数)的解析
Rule(link, callback='parse_item', follow=True),
)
# 解析方法
def parse_item(self, response):
# print(response.url)
dives = response.xpath('//div[@id="content-left"]/div')
for div in dives:
author = div.xpath('./div[@class="author clearfix"]/a[2]/h2/text()').extract_first()
print(author)

# CrawlSpider 类和 Spider 类的最大不同是 CrawlSpider 多了一个 rules 属性,
# 其作用是定义”提取动作“。在 rules 中可以包含一个或多个 Rule 对象,在 Rule 对象中包含了 LinkExtractor 对象。

使用例 2

import scrapy
import re
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class Study02Spider(CrawlSpider):
name = 'study02'
allowed_domains = ['ssr1.scrape.center']
start_urls = ['http://ssr1.scrape.center/']

# 定义提取 url 地址的规则
rules = (
# 在字符前面加个 r 表示正则表达式
# 提取出 url 地址的 response 会交给 callback 处理
Rule(LinkExtractor(allow=r'/detail/\d+'), callback='parse_item'),
# 翻页就无需回调了
Rule(LinkExtractor(allow=r'/page/\d+'), follow=True),
)

# 注意:这个 CrawlSpider 没有 parse 函数,也不能写 parse 函数,因为会覆盖掉父类的 parse 函数
def parse_item(self, response):
# 取得电影说明:
drama = re.search('[^\\n\s].?[^\\n]*',
response.xpath('//div[@class="el-card__body"]//div[@class="drama"]/p/text()').get()).group(0)
# 导演列表
directors = response.xpath(
'//div[@class="directors el-row"]//div[@class="el-card is-hover-shadow"]/div[@class="el-card__body"]')
directors_list = []
for ji in directors:
directors_list.append({
'director': ji.xpath('./p/text()').get(),
'director_img_url': ji.xpath('./img/@src').get()
})

# 演员列表
actors = response.xpath('//div[@class="actor el-col el-col-4"]')
actors_list = []
for actor in actors:
actors_list.append({
'actor_name': actor.xpath('.//p[1]/text()').get(),
'actor_image_url': actor.xpath('.//img/@src').get(),
'actor_role': re.search('[^饰:].*', actor.xpath('.//p[2]/text()').get()).group(0)
})
# 剧照地址
movie_poster = response.xpath('//div[@class="photos el-row"]')
movie_poster_list = []
for it in movie_poster:
movie_poster_list.append(it.xpath('.//img/@src').get())

return {
'directors_list': directors_list,
'drama': drama,
'actors_list': actors_list,
'movie_poster_list': movie_poster_list
}


if __name__ == '__main__':
from scrapy import cmdline

cmdline.execute("scrapy crawl study02".split())

下载中间件

Downloader Middlewares 是下载器完成 http 请求

首先需要先 Setting 文件里面打开

DOWNLOADER_MIDDLEWARES = {
'TempSpider.middlewares.TempDownloaderMiddleware': 543,
}

当每个 request 传递给下载中间件时会调用 process_request(self, request, spider)

当下载器完成 HTTP 请求,传递响应给引擎时会调用 process_response(self, request, response ,spider)

例如随机加上 User-Agent 请求头字段

先在 setting 文件里加上

USER_AGENTS = [
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0) ,Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Opera/9.25 (Windows NT 5.1; U; en), Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
]

然后就可以引入这个属性了

import random
from TempSpider.settings import USER_AGENTS
from scrapy import signals

# 所以可以在 process_request 里添加请求头
class TempDownloaderMiddleware:
def process_request(self, request, spider):
useragent = random.choice(USER_AGENTS)
request.headers["User-Agent"] = useragent

def process_response(self, request, response, spider):
print(request.headers["User-Agent"])
# 注意!! 这里要把 response 返回出去
return response

References